package com.botarena.server.services;

import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;

import org.python.compiler.Module;
import org.python.core.CompileMode;
import org.python.core.CompilerFlags;
import org.python.core.ParserFacade;

import com.botarena.server.dao.AccountDAO;
import com.botarena.server.dao.BotDAO;
import com.botarena.server.dao.filter.BotFilter;
import com.botarena.server.dao.helper.NonTransactional;
import com.botarena.server.dao.helper.NonTransactionalReturn;
import com.botarena.server.dao.helper.TransactionalReturn;
import com.botarena.server.helper.mappers.BotMapper;
import com.botarena.server.model.Account;
import com.botarena.server.model.Bot;
import com.botarena.server.model.Contest;
import com.botarena.server.services.validation.BotValidator;
import com.botarena.shared.BotInfo;
import com.botarena.shared.RankInfo;
import com.google.appengine.api.datastore.Blob;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;

/**
 * Service class implementing standard operations on bots.
 */
public class BotService extends GenericService<Bot, BotDAO, BotValidator> {
	
	private static final Logger log = Logger.getLogger(BotService.class.getName());

	public void create(final Bot bot, final Key accountKey, final Key contestKey) {
		DAOService.execute(new NonTransactional() {

			@Override
			public void doOutsideTransaction(PersistenceManager pm) {
				AccountDAO accountDAO = new AccountDAO(pm);
				BotDAO botDAO = new BotDAO(pm);

				bot.setAuthor(accountKey);
				bot.setContest(contestKey);

				if (!validator.isValid(bot))
					return;
					
				botDAO.create(bot);

				Account account = accountDAO.getByKey(accountKey);
				account.getBots().add(bot.getKey());
			}
		});
	}

	public Bot update(final Bot bot) {
		return DAOService.execute(new TransactionalReturn<Bot>() {

			@Override
			public Bot doInTransaction(PersistenceManager pm) {
				BotDAO botDAO = new BotDAO(pm);

				Bot entity = botDAO.getByKey(bot.getKey());

				entity.setName(bot.getName());
				entity.setParameters(bot.getParameters());
				entity.setContestant(bot.isContestant());

				if (!validator.isValid(entity))
					pm.currentTransaction().rollback();
				
				return entity;
			}
		});
	}

	public void delete(final Key botKey) {
		DAOService.execute(new NonTransactional() {

			@Override
			public void doOutsideTransaction(PersistenceManager pm) {
				AccountDAO accountDAO = new AccountDAO(pm);
				BotDAO botDAO = new BotDAO(pm);

				Bot bot = botDAO.getByKey(botKey);
				Account account = accountDAO.getByKey(bot.getAuthor());
				account.getBots().remove(bot.getKey());

				botDAO.delete(bot);
			}
		});
	}

	public void delete(final Bot bot) {
		delete(bot.getKey());
	}

	public List<BotInfo> getUserBots(final Key accountKey, final Key contestKey) {
		return DAOService.execute(new NonTransactionalReturn<List<BotInfo>>() {

			@Override
			public List<BotInfo> doOutsideTransaction(PersistenceManager pm) {
				AccountDAO accountDAO = new AccountDAO(pm);
				BotDAO botDAO = new BotDAO(pm);

				Account account = accountDAO.getByKey(accountKey);
				Set<Bot> userBots = new HashSet<Bot>(botDAO.getByKeys(account
						.getBots()));

				List<BotInfo> botInfos = new ArrayList<BotInfo>();
				for (Bot bot : userBots) {
					BotMapper mapper = new BotMapper();
					BotInfo botInfo = mapper.map(bot);
					botInfos.add(botInfo);
				}

				return botInfos;
			}
		});
	}

	public void compile(Bot bot) {
		
		try {
			String src = bot.getSourceCode();
			
			log.info("Compiling source: " + src);
			
			ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
			org.python.antlr.base.mod node = ParserFacade.parse(src, CompileMode.exec, "<script>", new CompilerFlags());
			Module.compile(node, byteStream, "code$py", "<script>", true, false, null, org.python.core.imp.NO_MTIME);
			bot.setBinary(new Blob(byteStream.toByteArray()));
			bot.setCompilationResults("OK");
			
		} catch (Exception e) {
			bot.setCompilationResults(e.toString());
		}
	}

	/**
	 * Zwraca liste botow (maksymalnie limit), ktore najwczesniej mialy walke
	 * (najdluzej czekaly na walke) w danym contescie.
	 */
	public Collection<Bot> getNotRecentlyFighting(final Contest contest,
			final int limit) {
		return DAOService
				.execute(new NonTransactionalReturn<Collection<Bot>>() {

					@Override
					@SuppressWarnings("unchecked")
					public Collection<Bot> doOutsideTransaction(
							PersistenceManager pm) {
						Query query = pm.newQuery(Bot.class);
						query.setRange(0, limit);
						query.setFilter("contest == contestKey && active == true && contestant == true && compilationResults == \"OK\"");
						query.setOrdering("lastFight ascending");
						query.declareParameters("Key contestKey");
						query.declareImports("import " + Key.class.getName());

						return pm.detachCopyAll((Collection<Bot>) query
								.execute(contest.getKey()));
					}
				});
	}

	public boolean canUserAddBot(Account account) {
		if (account.getBots().size() >= AccountService.MAX_NUMBER_OF_BOTS) {
			return false;
		} else {
			return true;
		}
	}

	public boolean isNameAvailable(Bot bot) {
		
		List<Bot> result = getAll(new BotFilter(bot.getName()));
		
		if (result != null && result.size() != 0) {
			if (result.get(0).getKey().equals(bot.getKey())) {
				return false;
			} else {
				return true;
			}
		} else {
			return false;
		}
	}

	public boolean isAuthor(Key accountKey, Key botKey) {
		Bot bot = getByKey(botKey);
		
		if (bot.getAuthor().equals(accountKey)) {
			return true;
		} else {
			return false;
		}
	}

	public List<RankInfo> getUserBotsInfo(final Key accountKey) {
		return DAOService.execute(new NonTransactionalReturn<List<RankInfo>>() {

			@Override
			public List<RankInfo> doOutsideTransaction(PersistenceManager pm) {
				AccountDAO accountDAO = new AccountDAO(pm);
				BotDAO botDAO = new BotDAO(pm);

				Account account = accountDAO.getByKey(accountKey);
				Set<Bot> userBots = new HashSet<Bot>(botDAO.getByKeys(account
						.getBots()));

				List<RankInfo> rankInfos = new ArrayList<RankInfo>();
				int pos = 1;
				for (Bot bot : userBots) {
					rankInfos.add(new RankInfo(pos++, bot.getName(), account.getName(), bot.getWins(), bot.getLosses(), bot.getDraws(), bot.getRank(), KeyFactory.keyToString(bot.getKey())));
				}

				return rankInfos;
			}
		});
	}
}
